#include "audio_output_openal.h"
#include "audio.h"
#include "audio_output_helper.h"
#include "thread.h"

#if defined(_MSC_VER)
#include <alc.h>
#include <al.h>
#elif defined(__APPLE__)
#include <OpenAL/alc.h>
#include <OpenAL/al.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif

// TODO: move to compiler settings
#pragma comment(lib, "openal32.lib")

using namespace krryn::audio;
using namespace krryn::audio_imp;

class output_openal::impl{
public:
	ALCcontext *m_Context;
	ALuint m_Source;
	enum { m_Frequency = 44100 } ;
};

output_openal::output_openal(){
	m_Pimpl = new impl();

	ALCdevice *l_Device = alcOpenDevice(NULL);

	if(!l_Device) throw exception("Can't create OpenAL device");

	// Request a fixed frequency at start up because the OpenAL spec
	// doesnt say how we can retrieve this information later on.
	int l_Attributes[] = {ALC_FREQUENCY, m_Pimpl->m_Frequency, 0};
	m_Pimpl->m_Context = alcCreateContext(l_Device, l_Attributes);

	if(!m_Pimpl->m_Context) throw exception("Can't create OpenAL context");

	alcMakeContextCurrent(m_Pimpl->m_Context);
	
	m_BufferCount = 32;
	m_Total = 0;

	alGenSources(1, &(m_Pimpl->m_Source));
}

output_openal::~output_openal(){
	ALCdevice *l_Device = alcGetContextsDevice(m_Pimpl->m_Context);
	alcDestroyContext(m_Pimpl->m_Context);
	alcCloseDevice(l_Device);

	delete m_Pimpl;
	m_Pimpl = 0;
}

int output_openal::sample_rate(){
	return m_Pimpl->m_Frequency;
}

void output_openal::write(sample::ptr a_Sample){
	audio::int_16 *l_Dest
		= output_helper::convert_for_output(a_Sample);

	ALuint l_Buffer = 0;

	if(m_Total < m_BufferCount){
		// enqueue a new buffer the first time we encounter this index
		alGenBuffers(1, &l_Buffer);
		alBufferData(
			l_Buffer, 
			AL_FORMAT_STEREO16, 
			l_Dest, 
			a_Sample->count() * sizeof(int_16), 
			a_Sample->sample_rate());
		alSourceQueueBuffers(m_Pimpl->m_Source, 1, &l_Buffer);
	}else{
		ALint l_Processed = 0;
		do{
			// wait until we have processed at least one source
			alGetSourcei(m_Pimpl->m_Source, AL_BUFFERS_PROCESSED, &l_Processed);

			// notice that the amount of buffers should be large enough 
			// to accomodate this.
			if(l_Processed == 0)
				core::thread::sleep(1);
		}while(l_Processed == 0);

		// request the free buffer
		alSourceUnqueueBuffers(m_Pimpl->m_Source, 1, &l_Buffer);

		// we know we have at least one processed buffer at this point,
		// so we can now enqueue this sample.
		alBufferData(
			l_Buffer, 
			AL_FORMAT_STEREO16, 
			l_Dest, 
			a_Sample->count() * sizeof(int_16), 
			a_Sample->sample_rate());

		alSourceQueueBuffers(m_Pimpl->m_Source, 1, &l_Buffer);
	}

	// If we're not playing while there we do have something left on the
	// queue, resume or start playback.
	ALint l_State;
	alGetSourcei(m_Pimpl->m_Source, AL_SOURCE_STATE, &l_State);
	if(l_State != AL_PLAYING){
		ALint l_Queued;
		alGetSourcei(m_Pimpl->m_Source, AL_BUFFERS_QUEUED, &l_Queued);
		if(l_Queued > 0){
			alSourcePlay(m_Pimpl->m_Source);
		}
	}

	m_Total++;
	delete[] l_Dest;
}